---
title: 핵심 컨셉
description: package:bloc의 핵심 개념에 대한 개요입니다.
sidebar:
  order: 1
---

import CountStreamSnippet from '~/components/concepts/bloc/CountStreamSnippet.astro';
import SumStreamSnippet from '~/components/concepts/bloc/SumStreamSnippet.astro';
import StreamsMainSnippet from '~/components/concepts/bloc/StreamsMainSnippet.astro';
import CounterCubitSnippet from '~/components/concepts/bloc/CounterCubitSnippet.astro';
import CounterCubitInitialStateSnippet from '~/components/concepts/bloc/CounterCubitInitialStateSnippet.astro';
import CounterCubitInstantiationSnippet from '~/components/concepts/bloc/CounterCubitInstantiationSnippet.astro';
import CounterCubitIncrementSnippet from '~/components/concepts/bloc/CounterCubitIncrementSnippet.astro';
import CounterCubitBasicUsageSnippet from '~/components/concepts/bloc/CounterCubitBasicUsageSnippet.astro';
import CounterCubitStreamUsageSnippet from '~/components/concepts/bloc/CounterCubitStreamUsageSnippet.astro';
import CounterCubitOnChangeSnippet from '~/components/concepts/bloc/CounterCubitOnChangeSnippet.astro';
import CounterCubitOnChangeUsageSnippet from '~/components/concepts/bloc/CounterCubitOnChangeUsageSnippet.astro';
import CounterCubitOnChangeOutputSnippet from '~/components/concepts/bloc/CounterCubitOnChangeOutputSnippet.astro';
import SimpleBlocObserverOnChangeSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeSnippet.astro';
import SimpleBlocObserverOnChangeUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeUsageSnippet.astro';
import SimpleBlocObserverOnChangeOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeOutputSnippet.astro';
import CounterCubitOnErrorSnippet from '~/components/concepts/bloc/CounterCubitOnErrorSnippet.astro';
import SimpleBlocObserverOnErrorSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnErrorSnippet.astro';
import CounterCubitOnErrorOutputSnippet from '~/components/concepts/bloc/CounterCubitOnErrorOutputSnippet.astro';
import CounterBlocSnippet from '~/components/concepts/bloc/CounterBlocSnippet.astro';
import CounterBlocEventHandlerSnippet from '~/components/concepts/bloc/CounterBlocEventHandlerSnippet.astro';
import CounterBlocIncrementSnippet from '~/components/concepts/bloc/CounterBlocIncrementSnippet.astro';
import CounterBlocUsageSnippet from '~/components/concepts/bloc/CounterBlocUsageSnippet.astro';
import CounterBlocStreamUsageSnippet from '~/components/concepts/bloc/CounterBlocStreamUsageSnippet.astro';
import CounterBlocOnChangeSnippet from '~/components/concepts/bloc/CounterBlocOnChangeSnippet.astro';
import CounterBlocOnChangeUsageSnippet from '~/components/concepts/bloc/CounterBlocOnChangeUsageSnippet.astro';
import CounterBlocOnChangeOutputSnippet from '~/components/concepts/bloc/CounterBlocOnChangeOutputSnippet.astro';
import CounterBlocOnTransitionSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionSnippet.astro';
import CounterBlocOnTransitionOutputSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionOutputSnippet.astro';
import SimpleBlocObserverOnTransitionSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionSnippet.astro';
import SimpleBlocObserverOnTransitionUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionUsageSnippet.astro';
import SimpleBlocObserverOnTransitionOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionOutputSnippet.astro';
import CounterBlocOnEventSnippet from '~/components/concepts/bloc/CounterBlocOnEventSnippet.astro';
import SimpleBlocObserverOnEventSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventSnippet.astro';
import SimpleBlocObserverOnEventOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventOutputSnippet.astro';
import CounterBlocOnErrorSnippet from '~/components/concepts/bloc/CounterBlocOnErrorSnippet.astro';
import CounterBlocOnErrorOutputSnippet from '~/components/concepts/bloc/CounterBlocOnErrorOutputSnippet.astro';
import CounterCubitFullSnippet from '~/components/concepts/bloc/CounterCubitFullSnippet.astro';
import CounterBlocFullSnippet from '~/components/concepts/bloc/CounterBlocFullSnippet.astro';
import AuthenticationStateSnippet from '~/components/concepts/bloc/AuthenticationStateSnippet.astro';
import AuthenticationTransitionSnippet from '~/components/concepts/bloc/AuthenticationTransitionSnippet.astro';
import AuthenticationChangeSnippet from '~/components/concepts/bloc/AuthenticationChangeSnippet.astro';
import DebounceEventTransformerSnippet from '~/components/concepts/bloc/DebounceEventTransformerSnippet.astro';

:::note

[`package:bloc`](https://pub.dev/packages/bloc)으로 작업하기 전에 다음 섹션을
주의 깊게 읽어주세요.

:::

Bloc 패키지 사용 방법을 이해하는 데 중요한 몇 가지 핵심 개념이 있습니다.

다음 섹션에서는 각 항목에 대해 자세하게 살펴보고 카운터 앱에 적용하는 방법을
살펴보겠습니다.

## Streams

:::note

`Streams`에 대한 자세한 내용은
[Dart 문서](https://dart.dev/tutorials/language/streams)를 참조하세요.

:::

Stream은 연속적인 비동기 데이터입니다.

Bloc 라이브러리를 사용하려면 `Streams`과 그 작동 방식에 대한 기본적인 이해가
필요합니다.

만약 당신이 `Streams`이 익숙하지 않다면, 물이 흐르는 파이프를 생각하면 됩니다.
파이프는 `Streams`이고 물은 비동기 데이터 입니다.

`async*` (비동기 생성기) 함수를 작성하여 Dart에서 `Stream`을 생성할 수 있습니다.

<CountStreamSnippet />

함수를 `async*`로 표시하면 `yield` 키워드를 사용하여 데이터의 `Stream`을 반환할
수 있습니다. 위 예시에서는 `max` 정수 파라미터까지의 정수 `Stream`을 반환하고
있습니다.

`async*` 함수에서 `yield` 할 때 마다 해당 데이터를 `Stream`을 통해 푸쉬합니다.

위의 `Stream`을 여러 가지 방법으로 사용할 수 있습니다. 만약 정수로 이루어진
`Stream`의 합계를 반환하는 함수를 작성하고 싶다면 다음과 같이 작성할 수
있습니다:

<SumStreamSnippet />

위의 함수를 `async`로 작성하면 `await` 키워드를 사용하여 정수의 `Future`를
반환할 수 있습니다. 이 예제에서는 Stream의 각 값을 기다렸다가 Stream에 있는 모든
정수의 합을 반환합니다.

위 모든 코드를 다음과 같이 사용할 수 있습니다:

<StreamsMainSnippet />

이제 Dart에서 `Streams`이 어떻게 작동하는지 기본적인 이해를 했으니, Bloc
패키지의 핵심 구성 요소: `Cubit`에 대해 알아볼 준비가 되었습니다.

## Cubit

`Cubit`은 `BlocBase`를 extends한 클래스로, 모든 유형의 state를 관리하도록 확장할
수 있습니다.

![Cubit Architecture](~/assets/concepts/cubit_architecture_full.png)

`Cubit`은 state의 변경을 촉발하기 위해 호출할 수 있는 함수를 외부로 노출시킬 수
있습니다.

State는 `Cubit`의 출력이며 애플리케이션 state의 일부를 나타냅니다. UI 컴포넌트는
state에 대한 notify를 받고, 현재 state에 따라 일부를 다시 그릴 수 있습니다.

:::note

`Cubit`의 기원에 대한 자세한 내용은
[해당 Issue](https://github.com/felangel/cubit/issues/69)에서 확인하세요.

:::

### Cubit 만들기

다음과 같은 `CounterCubit`을 만들 수 있습니다:

<CounterCubitSnippet />

`Cubit`을 생성할 때, `Cubit`이 관리할 상태의 타입을 정의해야 합니다. 위의
`CounterCubit`의 경우 state 타입은 `int`로 표현할 수 있지만, 더 복잡한 경우에는
Primitive type 대신 `class`를 사용해야 할 수도 있습니다.

`Cubit`을 생성할 때 두 번째로 해야 할 일은 초기 상태를 지정하는 것입니다. 초기
상태의 값으로 `super`를 호출하여 이를 수행할 수 있습니다. 위의 예시 코드는
내부적으로 초기 상태를 `0`으로 설정하고 있지만, 다음과 같이 외부의 값을 허용하여
`Cubit`이 더 유연하게 작동하도록 할 수도 있습니다.

<CounterCubitInitialStateSnippet />

이렇게 하면 다음과 같이 다양한 초기 상태를 가진 `CounterCubit` 인스턴스를 만들
수 있습니다.

<CounterCubitInstantiationSnippet />

### Cubit의 state변화

각 `Cubit`은 `emit`을 통해 새로운 state를 출력할 수 있습니다.

<CounterCubitIncrementSnippet />

위의 예시 코드에서 `CounterCubit`은 외부에서 호출하여 `CounterCubit`의 state를
증가시킬 수 있는 `increment` 라는 public 메서드를 노출하고 있습니다.
`increment`가 호출되면 `state` getter를 통해 `Cubit`의 현재 state에 접근하고,
현재 상태에 1을 더하여 새로운 state를 `emit`할 수 있습니다.

:::caution

`emit` 메서드는 protected 이므로 `Cubit` 내부에서만 사용해야 합니다.

:::

### Cubit 사용하기

이제 우리가 구현한 `CounterCubit`을 실제로 사용할 수 있습니다!

#### 기본 사용법

<CounterCubitBasicUsageSnippet />

위의 예시 코드에서는 먼저 `CounterCubit`의 인스턴스를 생성합니다. 그런 다음 초기
state인 Cubit의 현재 state를 출력합니다 (아직 새로운 state가 emit되지
않았으므로). 다음으로 `increment` 함수를 호출하여 state의 변경을 촉발합니다.
마지막으로 `0`에서 `1`로 바뀐 `Cubit`의 state를 다시 출력하고 `Cubit`의
`close`를 호출하여 내부 state stream을 닫습니다.

#### Stream 사용법

`Cubit`은 실시간 state 업데이트를 받을 수 있는 `Stream`을 노출합니다:

<CounterCubitStreamUsageSnippet />

위의 예시 코드에서는 `CounterCubit`을 구독하고 각 state 변경 시마다 print를
호출하고 있습니다. 그런 다음 새로운 state를 출력하는 `increment` 함수를 호출하고
있습니다. 마지막으로, 더 이상 업데이트를 받고 싶지 않을 때 `subscription`의
`cancel`을 호출하고 `Cubit`을 닫습니다.

:::note

해당 예제에서는 구독이 즉시 취소되지 않도록
`await Future.delayed(Duration.zero)`을 추가했습니다.

:::

:::caution

`Cubit`에서 `listen`을 호출할 때는 후속 상태 변경 사항만을 수신합니다.

:::

### Cubit 관찰하기

`Cubit`이 새로운 state를 emit하면 `Change`가 발생합니다. `onChange`를
override하여 주어진 `Cubit`에 대한 모든 변경 사항을 관찰할 수 있습니다.

<CounterCubitOnChangeSnippet />

그런 다음 `Cubit`과 상호 작용하고, 콘솔로 출력되는 모든 변경 사항을
관찰해봅시다.

<CounterCubitOnChangeUsageSnippet />

위 예시는 다음과 같이 출력됩니다:

<CounterCubitOnChangeOutputSnippet />

:::note

`Change`는 `Cubit`의 state가 업데이트되기 직전에 발생합니다. `Change`는
`currentState`와 `nextState`로 구성됩니다.

:::

#### BlocObserver

Bloc 라이브러리를 사용하면 한 곳에서 모든 `Change`에 접근할 수 있다는 장점이
있습니다. 이 애플리케이션은 하나의 `Cubit`만 있지만, 대규모 애플리케이션에서는
애플리케이션 state의 여러 부분을 관리하는 많은 `Cubit`을 사용하는 것이
일반적입니다.

모든 `Change`에 대응하여 무언가를 할 수 있도록 하려면 자체적으로
`BlocObserver`를 만들면 됩니다.

<SimpleBlocObserverOnChangeSnippet />

:::note

`BlocObserver`를 extends 하고 `onChange`메서드를 override 하기만 하면 됩니다.

:::

`SimpleBlocObserver`를 사용하려면 `main`함수를 조금만 수정하면 됩니다:

<SimpleBlocObserverOnChangeUsageSnippet />

위 예시 코드에 대한 출력입니다:

<SimpleBlocObserverOnChangeOutputSnippet />

:::note

내부의 `onChange` override가 먼저 호출되어 `BlocObserver`의 `onChange`를 알리는
`super.onChange`를 호출합니다.

:::

:::tip

`BlocObserver`에서는 `Change` 그 자체 외에도 `Cubit` 인스턴스에 접근할 수
있습니다.

:::

### Cubit의 에러 처리

모든 `Cubit`에는 에러가 발생했음을 나타내는데 사용할 수 있는 `addError` 메서드가
있습니다.

<CounterCubitOnErrorSnippet />

:::note

특정 `Cubit`에 대한 모든 에러를 처리하기 위해 `onError`를 `Cubit` 내에서
override할 수 있습니다.

:::

`BlocObserver`에서 `onError`를 override하여 보고된 모든 에러를 전역적으로 처리할
수도 있습니다.

<SimpleBlocObserverOnErrorSnippet />

동일한 프로그램을 다시 실행하면 다음과 같은 출력을 볼 수 있습니다:

<CounterCubitOnErrorOutputSnippet />

## Bloc

`Bloc`은 함수가 아닌 `event`에 의존하여 `state` 변경을 촉발하는 고급 클래스
입니다. `Bloc`은 또한 `BlocBase`를 extends하여 `Cubit`과 유사한 공용 API를 갖고
있습니다. 그러나, `Bloc`에서 `함수`를 호출하여 새로운 `state`를 직접 emit하는
대신, `Bloc`은 `event`를 수신하고 수신된 `event`를 나가는 `state`로 변환합니다.

![Bloc Architecture](~/assets/concepts/bloc_architecture_full.png)

### Bloc 만들기

`Bloc`을 생성하는 것은 `Cubit`를 생성하는 것과 비슷하지만, 관리할 state를
정의하는 것 외에 `Bloc`이 처리할 event도 정의해야 한다는 점이 다릅니다.

Event는 Bloc에 대한 입력입니다. 일반적으로 버튼을 누름과 같은 사용자 상호
작용이나, 페이지 로드와 같은 생명 주기 이벤트에 대한 응답으로 추가됩니다.

<CounterBlocSnippet />

`CounterCubit`을 생성할 때와 마찬가지로 `super`를 통해 부모 클래스에 전달하여
초기 state를 지정해야 합니다.

### Bloc의 state변화

`Bloc`은 `Cubit`의 함수가 아닌 `on<Event>` API를 통해 이벤트 핸들러를 등록해야
합니다. 이벤트 핸들러는 들어오는 모든 event를 0개 이상의 나가는 state로 변환하는
역할을 수행합니다.

<CounterBlocEventHandlerSnippet />

:::tip

`EventHandler`는 추가된 event뿐 만 아니라 수신되는 event에 대한 응답으로 0개
이상의 상태를 emit하는데 사용할 수 있는 `Emitter`에 접근할 수 있습니다.

:::

그런 다음 `EventHandler`를 업데이트하여 `CounterIncrementPressed` event를 처리할
수 있습니다:

<CounterBlocIncrementSnippet />

위의 예시 코드에서는 모든 `CounterIncrementPressed` event를 관리하기 위해
`EventHandler`를 등록했습니다. 들어오는 각 `CounterIncrementPressed` event에
대해 `state` getter와 `emit(state + 1)`를 통해 bloc의 현재 상태에 접근할 수
있습니다.

:::note

`Bloc` 클래스는 `BlocBase`를 extends 하기 떄문에 `Cubit`와 마찬가지로 `state`
getter를 통해 언제든지 bloc의 현재 state에 접근할 수 있습니다.

:::

:::caution

Bloc은 새로운 state를 직접 `emit`해서는 안 됩니다. 대신 모든 state 변경은
`EventHandler` 내에서 들어오는 event에 대한 응답으로 출력되어야 합니다.

:::

:::caution

Bloc과 Cubit 모두 복제된 상태를 무시합니다. `state == nextState`에서
`State nextState`를 emit하면 state 변경이 발생하지 않습니다.

:::

### Bloc 사용하기

이 시점에서 `CounterBloc`의 인스턴스를 생성하여 사용할 수 있습니다!

#### 기본 사용법

<CounterBlocUsageSnippet />

위의 예시 코드에서는 먼저 `CounterBloc`의 인스턴스를 생성합니다. 그런 다음 초기
state인 `Bloc`의 현재 state를 출력합니다 (아직 새로운 state가 emit되지
않았으므로). 다음으로 state 변경을 촉발하기 위해 `CounterIncrementPressed`
event를 추가합니다. 마지막으로 `0`에서 `1`로 변경된 `Bloc`의 state를 다시
출력하고 `Bloc`의 `close`룰 호출하여 내부 state stream을 닫습니다.

:::note

다음 event-loop를 기다리기 위해 `await Future.delayed(Duration.zero)`가
추가됩니다 (`EventHandler`가 event를 처리할 수 있도록 허용).

:::

#### Stream 사용법

`Cubit`과 마찬가지로 `Bloc`은 `Stream`의 특수한 유형으로, `Bloc`을 구독하여
state를 실시간으로 업데이트 할 수도 있습니다:

<CounterBlocStreamUsageSnippet />

위의 예시 코드에서는 `CounterBloc`을 구독하고 각 state 변경 시마다 print를
호출하고 있습니다. 그런 다음 `on<CounterIncrementPressed>` `EvnetHandler`를
촉발하고 새 state를 emit하는 `CounterIncrementPressed` event를 추가하고
있습니다. 마지막으로, 더 이상 업데이트를 받지 않으려면 `subscription`의
`cancel`을 호출하고 `Bloc`을 닫습니다.

:::note

해당 예제에서는 구독이 즉시 취소되지 않도록
`await Future.delayed(Duration.zero)`을 추가했습니다.

:::

### Bloc 관찰하기

`Bloc`은 `BlocBase`를 extends 하기 때문에 `onChange`를 사용하여 `Bloc`의 모든
state 변화를 관찰할 수 있습니다.

<CounterBlocOnChangeSnippet />

그런 다음 `main.dart`를 다음과 같이 업데이트 합니다:

<CounterBlocOnChangeUsageSnippet />

위 예시 코드에 대한 출력입니다:

<CounterBlocOnChangeOutputSnippet />

`Bloc`과 `Cubit`의 주요 차별화 요소 중 하나는 `Bloc`이 event 기반이기 때문에
state 변화를 유발한 원인에 대한 정보도 캡처할 수 있다는 점입니다.

이 작업은 `onTransition`을 override하여 수행할 수 있습니다.

한 state에서 다른 state로 변경되는 것을 `Transition`이라고 합니다.
`Transition`은 현재 state, event, 다음 state로 구성됩니다.

<CounterBlocOnTransitionSnippet />

그런 다음 이전과 동일한 `main.dart` 예시 코드를 다시 실행하면 다음과 같은 출력이
표시됩니다.

<CounterBlocOnTransitionOutputSnippet />

:::note

`onTransition`은 `onChange` 이전에 호출되며 `currentState`에서 `nextState`로
변경을 촉발한 event를 포함합니다.

:::

#### BlocObserver

이전과 마찬가지로 커스텀 `BlocObserver`에서 `onTransition`을 override하여 단일
위치에서 발생하는 모든 Transition을 관찰할 수 있습니다.

<SimpleBlocObserverOnTransitionSnippet />

이전과 마찬가지로 `SimpleBlocObserver`를 초기화 할 수 있습니다:

<SimpleBlocObserverOnTransitionUsageSnippet />

이제 위의 예시 코드를 실행하면 다음과 같은 출력을 얻을 수 있습니다:

<SimpleBlocObserverOnTransitionOutputSnippet />

:::note

`onTransition` 이 먼저 호출되고(global보다 local이 먼저) 그 다음에 `onChange`가
호출됩니다.

:::

`Bloc` 인스턴스의 또 다른 독특한 특징은 `Bloc`에 새 event가 추가될 때마다
호출되는 `onEvnet`를 override할 수 있다는 점입니다. `onChange` 및
`onTransition`과 마찬가지로 `onEvent`는 전역뿐만 아니라 로컬에서도 override할 수
있습니다.

<CounterBlocOnEventSnippet />

<SimpleBlocObserverOnEventSnippet />

이전과 동일한 `main.dart`를 실행하면 다음과 같은 출력을 볼 수 있습니다:

<SimpleBlocObserverOnEventOutputSnippet />

:::note

`onEvent`는 event가 추가되는 즉시 호출됩니다. local `onEvent`는 `BlocObserver`의
global `onEvent`보다 먼저 호출됩니다.

:::

### Bloc의 에러 처리

`Cubit`과 마찬가지로 각 `Bloc`에는 `addError`와 `onError` 메서드가 있습니다.
`Bloc` 내부 어디에서나 `addError`를 호출하여 에러가 발생했음을 알릴 수 있습니다.
그런 다음 `Cubit`과 마찬가지로 `onError`를 override 하여 모든 에러에 대응할 수
있습니다.

<CounterBlocOnErrorSnippet />

이전과 동일한 `main.dart`를 다시 실행하면 에러가 보고될 때 어떤 모습인지 확인할
수 있습니다:

<CounterBlocOnErrorOutputSnippet />

:::note

local `onError`가 먼저 호출된 후 `BlocObserver`의 global `onError`가 호출됩니다.

:::

:::note

`onError`와 `onChange`는 `Bloc`과 `Cubit` 인스턴스 모두에 대해 똑같은 방식으로
작동합니다.

:::

:::caution

`EventHandler` 내에서 발생하는 처리되지 않은 exception도 `onError`에서
보고됩니다.

:::

## Cubit vs. Bloc

이제 `Cubit`과 `Bloc` 클래스의 기본 사항을 살펴봤으니 언제 `Cubit`을 사용해야
하는지, 언제 `Bloc`을 사용해야 하는지 궁금하실 것입니다.

### Cubit 장점

#### 단순성

`Cubit` 사용의 가장 큰 장점 중 하나는 단순성입니다. `Cubit`을 생성할 때는
state와 state를 변경하기 위해 노출할 함수만 정의하면 됩니다. 반면에 `Bloc`을
생성할 때는 state, event, `EventHandler` 구현을 정의해야 합니다. 따라서
`Cubit`을 더 쉽게 이해할 수 있고 관련된 코드도 더 적습니다.

이제 두 가지 카운터 구현을 살펴보겠습니다:

##### CounterCubit

<CounterCubitFullSnippet />

##### CounterBloc

<CounterBlocFullSnippet />

`Cubit` 구현은 더 간결하며 event를 별도로 정의하는 대신 함수가 event처럼
작동합니다. 또한, `Cubit`을 사용할 때는 어디서든 `emit`을 호출하여 state 변경을
촉발할 수 있습니다.

### Bloc 장점

#### 추적가능성

`Bloc` 사용의 가장 큰 장점 중 하나는 state 변경의 순서와 그 변경을 유발한 원인을
정확히 파악할 수 있다는 점입니다. 애플리케이션의 기능에 중요한 state의 경우
state 변경 외에도 모든 event를 캡처하기 위해, 보다 event 중심적인 접근 방식을
사용하는 것이 매우 유용할 수 있습니다.

일반적인 사용 사례는 `AuthenticationState`를 관리하는 것입니다. 간단히 설명하기
위해 `enum`을 통해 `AuthenticationState`를 표현할 수 있다고 가정해 보겠습니다:

<AuthenticationStateSnippet />

애플리케이션의 state가 `authenticated`에서 `unauthenticated`로 변경되는 이유는
여러 가지가 있을 수 있습니다. 예로 들어 사용자가 로그아웃 버튼을 탭하고
애플리케이션에서 로그아웃을 요청했을 수 있습니다. 반면에 사용자의 access token이
해지되어 강제로 로그아웃되었을 수도 있습니다. `Bloc`을 사용하면 애플리케이션
state가 특정 state에 도달한 경로를 명확하게 추적할 수 있습니다.

<AuthenticationTransitionSnippet />

위의 `Transition`은 state가 변경된 이유를 이해하는데 필요한 모든 정보를
제공합니다. 만약 `Cubit`을 사용해 `AuthenticationState`를 관리했다면, 로그는
다음과 같이 보일 것입니다:

<AuthenticationChangeSnippet />

이는 사용자가 로그아웃되었다는 사실을 알려주지만, 시간이 지남에 따라 상태가
어떻게 변하는지 디버깅하고 이해하는데 무엇이 중요한지 설명하지 않습니다.

#### 고급 Event Transformations

`Bloc`이 `Cubit`보다 뛰어난 또 다른 영역은 `buffer`, `debounceTime`, `throttle`
등과 같은 반응형 연산자를 활용해야 하는 경우입니다.

`Bloc`에는 들어오는 event의 흐름을 제어하고 변환할 수 있는 event sink가
있습니다.

예를 들어, 실시간 검색을 구축하는 경우 속도 제한을 피하고 백엔드의 비용/부하를
줄이기 위해 백엔드에 대한 요청을 debouncing하고 싶을 것입니다.

`Bloc`을 사용하면 `Bloc`이 수신 event를 처리하는 방식을 변경하는 커스텀
`EventTransformer`를 제공할 수 있습니다.

<DebounceEventTransformerSnippet />

위의 코드를 사용하면 추가 코드를 거의 추가하지 않고도 수신 event를 쉽게
debounce할 수 있습니다.

:::tip

EventTransformer에 대한 자세한 내용은
[`package:bloc_concurrency`](https://pub.dev/packages/bloc_concurrency)를
확인하세요.

:::

어떤 것을 사용해야 할지 잘 모르겠다면 `Cubit`으로 시작하고 나중에 필요에 따라
`Bloc`으로 리펙토링하거나 스케일업할 수 있습니다.
